﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Miris.HumanMilkAnalyser.DataModel;
using Miris.HumanMilkAnalyser.Repository.Interface;
using Miris.HumanMilkAnalyser.Service.Interface;
using Miris.HumanMilkAnalyser.Service.Shared;
using Miris.HumanMilkAnalyser.Service.Validation;

namespace Miris.HumanMilkAnalyser.Service
{
    public class PatientService : ServiceBase<Patient>, IPatientService
    {
        private IPatientRepository _patientRepository;

        public PatientService(IRepositoryFactory repositoryFactory) : base(repositoryFactory, new PatientValidator()) { }

        /// <summary>
        /// Query the matched Patient data with the provided Patient & AnalysisResult info.
        /// </summary>
        /// <param name="patient">The provided Patient contains the data field used to search. For example, the Number property.</param>
        /// <param name="analysisResult">The provided AnalysisResult contains the data field used to search. For example, the SampleNumber property.</param>
        /// <param name="selectedStartDate">The selected start date used to identify the AnalysisResult happened after the specific date.</param>
        /// <param name="selectedEndDate">The selected start date used to identify the AnalysisResult happened before the specific date.</param>
        /// <returns>The matched Patient data list.</returns>
        public virtual IList<Patient> Query(Patient patient, AnalysisResult analysisResult, DateTime? selectedStartDate, DateTime? selectedEndDate)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                _patientRepository = RepositoryFactory.CreatePatientRepository();

                // Get the matched Patients based on provided Patient info
                var matchedPatients = _patientRepository.Query(p => (patient.Number.Length == 0 || p.Number.ToLower().Contains(patient.Number.ToLower())) &&
                    (patient.MotherName.Length == 0 || p.MotherName.Contains(patient.MotherName)));

                // Get the matched Patients based on AnalysisResult info if provided
                if (analysisResult != null && selectedStartDate.HasValue && selectedEndDate.HasValue)
                {
                    matchedPatients = from p in matchedPatients
                                      join a in RepositoryFactory.CreateAnalysisResultRepository().GetAll() on p.Number equals a.Patient.Number
                                      where (analysisResult.SampleNumber.Length == 0 || a.SampleNumber.Contains(analysisResult.SampleNumber)) &&
                                      a.TestDate >= selectedStartDate.Value && a.TestDate <= selectedEndDate.Value
                                      select p;
                }

                return matchedPatients.ToList();
            }
        }

        /// <summary>
        /// Delete the specified entity.
        /// </summary>
        /// <param name="entity">The entity will be deleted from database.</param>
        public override void Delete(Patient entity)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                _patientRepository = RepositoryFactory.CreatePatientRepository();

                // Delete all related analysis data
                DeletePatientAnalysisData(entity);

                // Delete current Patient data.
                _patientRepository.Delete(entity);

                RepositoryFactory.Commit();
            }
        }

        /// <summary>
        /// Delete the specified entity list.
        /// </summary>
        /// <param name="entities">The entity list will be deleted from database.</param>
        public override void Delete(IEnumerable<Patient> entities)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                _patientRepository = RepositoryFactory.CreatePatientRepository();

                foreach (var entity in entities)
                {
                    // Delete all related analysis data
                    DeletePatientAnalysisData(entity);

                    // Delete current Patient data.
                    _patientRepository.Delete(entity);
                }

                RepositoryFactory.Commit();
            }
        }

        /// <summary>
        /// Get the generated patient number based on configured rule.
        /// </summary>
        /// <returns>The generated patient number meets configured rule.</returns>
        public string GetPatientNumber()
        {
            var numberHelper = new ConfiguredNumberHelper(ConfigurationConstant.PatientNumberPrefix,
                ConfigurationConstant.PatientNumberContainsYear,
                ConfigurationConstant.PatientNumberContainsMonth,
                ConfigurationConstant.PatientNumberContainsDay,
                ConfigurationConstant.PatientNumberInitializedSequence);

            var number = string.Empty;

            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                var patientRepository = RepositoryFactory.CreatePatientRepository();

                // Get current max patient number based on provided full prefix.

                IQueryable<Patient> matchedPatients = patientRepository.Query(p => p.Number.Length == numberHelper.FixedLength);

                if (!string.IsNullOrEmpty(numberHelper.FullPrefix))
                {
                    // If contains prefix.

                    matchedPatients = matchedPatients.Where(p => p.Number
                        .StartsWith(numberHelper.FullPrefix));
                }

                var currentPatientWithMaxNumber = matchedPatients.OrderByDescending(p => p.Number).Take(1).FirstOrDefault();

                // Get the new generated sample number.
                number = numberHelper.GenerateNewNumber(currentPatientWithMaxNumber == null ? string.Empty : currentPatientWithMaxNumber.Number);
            }

            return number;
        }

        /// <summary>
        /// Get all patient data and all related analysis results.
        /// </summary>
        /// <returns></returns>
        public IList<Patient> GetAllWithAnalysisResults()
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                _patientRepository = RepositoryFactory.CreatePatientRepository();

                return _patientRepository.GetAll("AnalysisResults;").ToList();
            }
        }

        /// <summary>
        /// Delete all related analysis data ONLY of specified Patient.
        /// </summary>
        /// <param name="testTrackingRepository">Instance of class that implements ITestTrackingRepository inteface.</param>
        /// <param name="patient">The specified Patient whom all related test data will be deleted.</param>
        private void DeletePatientAnalysisData(Patient patient)
        {
            // Get all related TestTracking data.
            var analysisResults = RepositoryFactory.CreateAnalysisResultRepository().Query(t => t.PatientNumber == patient.Number,
                "AnalysisResultDiagnosticMessages");

            foreach (var analysisResult in analysisResults)
            {
                CommonService.DeleteAnalysisResult(RepositoryFactory, analysisResult);
            }
        }
    }
}
